/**
 * Copyright 2019 吉鼎科技.
 *
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.web.task.event;

import cn.easyplatform.EasyPlatformWithLabelKeyException;
import cn.easyplatform.web.contexts.Contexts;
import cn.easyplatform.web.dialog.ActionEvent;
import cn.easyplatform.web.dialog.MessageBox;
import cn.easyplatform.web.log.LogManager;
import cn.easyplatform.web.task.BackendException;
import cn.easyplatform.web.task.EventSupport;
import cn.easyplatform.web.task.MainTaskSupport;
import cn.easyplatform.web.task.OperableHandler;
import cn.easyplatform.web.task.support.ManagedComponent;
import cn.easyplatform.web.task.support.ManagedComponents;
import cn.easyplatform.web.task.support.ScriptEngine;
import cn.easyplatform.web.task.support.SupportFactory;
import cn.easyplatform.web.task.support.scripting.EventExitException;
import cn.easyplatform.web.task.support.scripting.ScriptEvalException;
import cn.easyplatform.web.task.zkex.ListSupport;
import cn.easyplatform.web.task.zkex.list.PanelSupport;
import cn.easyplatform.web.utils.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zkoss.util.resource.Labels;
import org.zkoss.zk.ui.*;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.util.Clients;
import org.zkoss.zul.ListModel;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @since 2.0.0 <br/>
 */
public class EventListenerHandler implements EventListener<Event> {

    private static final Logger log = LoggerFactory.getLogger(EventListenerHandler.class);

    private EventSupport handler;

    private String script;

    private ScriptEngine engine;

    protected int line = 1;

    private Map<String, Object> variables;

    private boolean result;

    private Component anchor;//如果是列表发出的事件，事件地应的锚步

    private String eventName;

    public EventListenerHandler(String eventName, EventSupport handler, String script) {
        this(eventName, handler, script, null);
    }

    public EventListenerHandler(String eventName, EventSupport handler, String script, Component anchor) {
        this.eventName = eventName;
        this.handler = handler;
        this.anchor = anchor;
        this.script = script;
    }

    @Override
    public void onEvent(final Event event) throws Exception {
        if (anchor != null)
            WebUtils.setAnchor(anchor);
        if (engine == null)
            engine = createEngine(event);
        else if (handler != null) {
            if (event instanceof ActionEvent) {
                ActionEvent evt = (ActionEvent) event;
                setActionFlag(evt.getActionFlag(), evt.getId());
                Contexts.setEvent(Event.class, (Event) variables.get("event"));
                call(event);
                return;
            } else {
                for (Object c : variables.values()) {
                    if (c instanceof EventDispatcher) {
                        EventDispatcher dispatcher = (EventDispatcher) c;
                        dispatcher.set(0, handler.getId());
                        dispatcher.set(event);
                    }
                }
                Object obj = variables.remove("event");
                if (log.isDebugEnabled())
                    log.debug("remove->" + obj);
                obj = null;
                obj = variables.remove("self");
                obj = null;
                variables.put("self", event.getTarget());
                variables.put("event", event);
                Contexts.setEvent(Event.class, event);
            }
        } else {
            Object obj = variables.remove("event");
            if (log.isDebugEnabled())
                log.debug("remove->" + obj);
            obj = null;
            variables.put("event", event);
        }

        if (event.getTarget().getAttribute("longOpertion") != null) {
            String message = (String) event.getTarget().getAttribute("longOpertion");
            if ("true".equalsIgnoreCase(message))
                Clients.showBusy(Labels.getLabel("message.long.operation"));
            else
                Clients.showBusy(message);
            event.getTarget().addEventListener("onLater", new EventListener<Event>() {
                @Override
                public void onEvent(Event evt) throws Exception {
                    try {
                        call(event);
                    } finally {
                        event.getTarget().removeEventListener("onLater", this);
                        Clients.clearBusy();
                    }
                }
            });
            Events.echoEvent("onLater", event.getTarget(), null);
        } else
            call(event);
    }

    private void call(Event event) throws Exception {
        try {
            LogManager.beginRequest();
            Contexts.setEventListenerHandler(this);
            if (log.isDebugEnabled())
                log.debug("variables->" + variables);
            boolean actionFlag = event instanceof ActionEvent;
            result = false;
            if (!actionFlag) {
                String prefixScript = null;
                if (handler instanceof MainTaskSupport) {
                    MainTaskSupport ts = (MainTaskSupport) handler;
                    prefixScript = ts.getScript();
                } else {
                    ListSupport support = null;
                    if (handler instanceof ListSupport)
                        support = (ListSupport) handler;
                    else if (handler instanceof PanelSupport)
                        support = ((PanelSupport) handler).getList();
                    if (support != null && support.getMainHandler().getParent() instanceof MainTaskSupport)
                        prefixScript = ((MainTaskSupport) support.getMainHandler().getParent()).getScript();
                }
                engine.init(variables, prefixScript, script);
            }
            engine.eval(line);
            result = true;
            line = 1;
            if (actionFlag)
                setActionFlag(0, handler.getId());
        } catch (PauseException ex) {
            line = ex.getLine();
        } catch (ContinueException ex) {
            onEvent(event);
        } catch (BackendException ex) {
            char code = ex.getMsg().getCode().toLowerCase().charAt(0);
            if (code == 'c' || code == 'w' || code == 'i') {
                line = ex.getLine();
            } else {
                line = 1;
                engine.destory();
            }
            MessageBox.createDialog(ex.getMsg(), this).show();
        } catch (EasyPlatformWithLabelKeyException ex) {
            line = 1;
            engine.destory();
            MessageBox.showMessage(ex);
            if (ex.getCause() != null)
                log.error("eval", ex.getCause());
        } catch (ScriptEvalException ex) {
            line = 1;
            engine.destory();
            if (ex.getCause() != null
                    && ex.getCause() instanceof WrongValueException) {
                WrongValueException we = (WrongValueException) ex.getCause();
                if (we.getComponent() != null)
                    ((HtmlBasedComponent) we.getComponent()).setFocus(true);
                throw we;
            } else {
                log.error("eval", ex.getCause());
                MessageBox
                        .showMessage(Labels
                                .getLabel("message.dialog.title.error"), Labels
                                .getLabel(
                                        "script.eval.error",
                                        new Object[]{ex.getLine(),
                                                ex.getMessage()}));
            }
        } catch (EventExitException ex) {
            line = 1;
            engine.destory();
        } catch (Exception ex) {
            line = 1;
            engine.destory();
            if (log.isErrorEnabled())
                log.error("eval", ex);
            MessageBox.showMessage(Labels
                    .getLabel("message.dialog.title.error"), Labels.getLabel("script.eval.fatal", new Object[]{ex.getMessage()}));
        } finally {
            Contexts.clear();
            LogManager.endRequest();
        }
    }

    protected void setActionFlag(int actionFlag, String id) {
        for (Object c : variables.values()) {
            if (c instanceof EventDispatcher) {
                EventDispatcher dispatcher = (EventDispatcher) c;
                dispatcher.set(actionFlag, id == null ? handler.getId() : id);
            }
        }
    }

    private ScriptEngine createEngine(Event event) {
        Contexts.setEvent(Event.class, event);
        variables = new HashMap<String, Object>();
        variables.put("self", event.getTarget());
        variables.put("event", event);
        if (handler != null) {
            variables.put("$", new EventDispatcher(handler, variables,
                    this, event));
            OperableHandler parent = null;
            if (handler instanceof ManagedComponents) {// 受管的组件
                ManagedComponents man = (ManagedComponents) handler;
                parent = man.getParent();
                Set<Entry<String, Component>> set = man.getManagedComponents().entrySet();
                for (Entry<String, Component> entry : set)
                    variables.put(entry.getKey(), entry.getValue());
            }
            if (handler instanceof MainTaskSupport) {//主页面
                MainTaskSupport ts = (MainTaskSupport) handler;
                Page page = ts.getComponent().getPage();
                if (page == null)
                    page = event.getTarget().getPage();
                if (page == null)
                    page = Executions.getCurrent().getDesktop().getFirstPage();
                if (page.getXelVariable("$easyplatform") != null)
                    variables.put("$easyplatform", page.getXelVariable("$easyplatform"));
                for (Map.Entry<String, Object> entry : ts.getComponent().getAttributes().entrySet()) {
                    if (entry.getValue() instanceof ListModel<?>)//绑定的变量
                        variables.put(entry.getKey(), entry.getValue());
                }
                for (ManagedComponent es : ts.getManagedEntityComponents()) {
                    if (es instanceof ListSupport) {
                        ListSupport ods = (ListSupport) es;
                        variables.put(ods.getComponent().getId(), new EventDispatcher(ods, variables,
                                this, event));
                        Set<Entry<String, Component>> set = ods
                                .getManagedPanelComponents().entrySet();
                        for (Entry<String, Component> entry : set)
                            variables.put(ods.getComponent().getId()
                                            + "$" + entry.getKey(),
                                    entry.getValue());
                    } else {
                        variables.put(es.getComponent().getId(), es);
                    }
                }
                // 设置子功能，由<taskbox>生成的功能
                for (Map.Entry<String, MainTaskSupport> entry : ts.getChildren()
                        .entrySet())
                    setChildVariable(event, entry.getKey(), entry.getValue());
                if (parent != null)
                    setParentVariable(event, "parent", parent,
                            (OperableHandler) handler);
                else
                    variables.put("parent", null);
            } else {//列表
                ListSupport ls = null;
                if (handler instanceof ListSupport)
                    ls = (ListSupport) handler;
                else if (handler instanceof PanelSupport)
                    ls = ((PanelSupport) handler).getList();
                if (ls.getAnchor() != null)
                    WebUtils.setAnchor(ls.getAnchor());
                variables.put("parent", new EventDispatcher(parent, variables, this, event));
                Set<Entry<String, Component>> set = parent
                        .getManagedComponents().entrySet();
                for (Entry<String, Component> entry : set)
                    variables.put("parent$" + entry.getKey(), entry.getValue());
                if (parent instanceof MainTaskSupport) {
                    MainTaskSupport ts = (MainTaskSupport) parent;
                    variables.put("$easyplatform", ts.getComponent().getAttribute("$easyplatform"));
                    for (Map.Entry<String, Object> entry : ts.getComponent().getAttributes().entrySet()) {
                        if (entry.getValue() instanceof ListModel<?>)//绑定的变量
                            variables.put("parent$" + entry.getKey(), entry.getValue());
                    }
                    for (ManagedComponent es : ts.getManagedEntityComponents()) {
                        if (handler != es) {
                            if (es instanceof ListSupport) {
                                ListSupport ods = (ListSupport) es;
                                variables.put(ods.getComponent().getId(), new EventDispatcher(ods, variables,
                                        this, event));
                                for (Entry<String, Component> entry : ods
                                        .getManagedPanelComponents().entrySet())
                                    variables.put(ods.getComponent().getId()
                                                    + "$" + entry.getKey(),
                                            entry.getValue());
                            } else {
                                variables.put("parent$" + es.getComponent().getId(), es);
                            }
                        }
                    }
                    // 设置子功能，由<taskbox>生成的功能
                    for (Map.Entry<String, MainTaskSupport> entry : ts.getChildren()
                            .entrySet())
                        setChildVariable(event, "parent$" + entry.getKey(), entry.getValue());
                }
                EventSupport self = parent;
                parent = parent.getParent();
                if (parent != null)
                    setParentVariable(event, "parent$parent", parent,
                            (OperableHandler) self);
            }
        } else {
            EventDispatcher dispatcher = new EventDispatcher(this, variables,
                    event);
            variables.put("$", dispatcher);
        }
        engine = SupportFactory.getScriptEngine();
        return engine;
    }

    private void setChildVariable(Event event, String prefix, MainTaskSupport child) {
        variables.put(prefix, new EventDispatcher(child, variables,
                this, event));
        prefix += "$";
        if (child.getManagedEntityComponents() != null) {
            Set<Entry<String, Component>> set = child.getManagedComponents().entrySet();
            for (Entry<String, Component> entry : set)
                variables.put(prefix + entry.getKey(), entry.getValue());
        }
        for (ManagedComponent es : child.getManagedEntityComponents()) {
            if (es instanceof ListSupport) {
                ListSupport ods = (ListSupport) es;
                variables.put(prefix + ods.getComponent().getId(), new EventDispatcher(ods, variables,
                        this, event));
                for (Entry<String, Component> entry : ods
                        .getManagedPanelComponents().entrySet())
                    variables.put(prefix + ods.getComponent().getId()
                                    + "$" + entry.getKey(),
                            entry.getValue());
            } else {
                variables.put(prefix + es.getComponent().getId(), es);
            }
        }
        // 设置子功能，由<taskbox>生成的功能
        for (Map.Entry<String, MainTaskSupport> entry : child.getChildren()
                .entrySet())
            setChildVariable(event, prefix + entry.getKey(), entry.getValue());
    }

    private void setParentVariable(Event event, String prefix, OperableHandler parent, OperableHandler child) {
        variables.put(prefix, new EventDispatcher(parent, variables,
                this, event));
        prefix += "$";
        if (parent.getManagedComponents() != null) {
            Set<Entry<String, Component>> set = parent.getManagedComponents().entrySet();
            for (Entry<String, Component> entry : set)
                variables.put(prefix + entry.getKey(), entry.getValue());
        }
        if (parent instanceof MainTaskSupport) {
            for (ManagedComponent es : ((MainTaskSupport) parent).getManagedEntityComponents()) {
                if (es != child) {
                    if (es instanceof ListSupport) {
                        ListSupport ods = (ListSupport) es;
                        variables.put(prefix + ods.getComponent().getId(), new EventDispatcher(ods, variables,
                                this, event));
                        for (Entry<String, Component> entry : ods
                                .getManagedPanelComponents().entrySet())
                            variables.put(prefix + ods.getComponent().getId()
                                            + "$" + entry.getKey(),
                                    entry.getValue());
                    } else {
                        variables.put(prefix + es.getComponent().getId(), es);
                    }
                }
            }
            // 设置子功能，由<taskbox>生成的功能
            for (Map.Entry<String, MainTaskSupport> entry : ((MainTaskSupport) parent).getChildren()
                    .entrySet())
                setChildVariable(event, prefix + entry.getKey(), entry.getValue());
        }

        if (parent.getParent() != null)
            setParentVariable(event, prefix + "parent", parent.getParent(), parent);
    }

    public boolean isSuccess() {
        return result;
    }

    public EventSupport getHandler() {
        return handler;
    }

    public Component getAnchor() {
        return anchor;
    }

    public String getName() {
        return eventName;
    }
}
